Flutter js 之 QuickJsRuntime2
QuickJsRuntime2 是 flutter_js 对 QuickJS 引擎的封装,在 Android 下默认为 QuickJS,允许手动切换为 JavaScriptCore。
本文主要研究该类中一些核心功能的实现。
evaluate 资源释放
该类的 evaluate 方法用于执行 JavaScript 代码。
其内部通过 QuickJS 的 jsEval API 实现。
并通过内置的 _jsToDart
方法,实现将结果从 JS 转为 Dart 类型。
资源释放问题
这个 PR 指出,evaluate 的结果用完后没有释放,需要手动释放:
jsFreeValue(ctx,jsval);
如果不加这行代码,会导致内存泄漏。
引擎释放
QuickJsRuntime2 的 dispose 方法为空,没有资源释放逻辑,而是作为一个 TODO 待后续完成。
该 PR 同样给出了引擎的释放逻辑:
@override
void dispose() {
try {
port.close(); // stop dispatch loop
close(); // close engine
} on JSError catch (e) {
print(e); // catch reference leak exception
}
}
释放报错
我实用上述代码,在释放时遇到了 QuickJS 报错:
quickjs.c:2019: void JS_FreeRuntime(JSRuntime *): assertion "list_empty(&rt->gc_obj_list)" failed.
通过下面两个 issue 可以看出:
- Assertion Error on `JS_FreeRuntime` (Memory Leak) · Issue #44 · bellard/quickjs (github.com)
- Modifying prototype causes memory leak (assertion in JS_FreeRuntime) · Issue #66 · bellard/quickjs (github.com)
如果有底层内存泄漏,QuickJS 在释放时会报这个错误。
经过验证,这里的报错与 evaluate 中的 jsFreeValue 是相关的:
- 如果我在 evaluate 时没有 jsFreeValue,在引擎释放时,断言发现有泄漏对象报错
- 如果我在 evaluate 时完成 jsFreeValue,在引擎释放时,可以释放成功
Reference Leak
在解决了释放报错问题后,在释放 QuickJS 引擎时,捕获到一处异常:
[ +11 ms] I/flutter (11005): QuickJsRuntime2 dispose jsError = reference leak:
[ ] I/flutter (11005): ADDR REF TYPE PROP
[ ] I/flutter (11005): 743331434 1 _JSFunction (key, val) => { this[key] = val; } ...
[ ] I/flutter (11005): 77684463 1 _JSFunction function FLUTTER_NATIVEJS_MakeQuerablePromise(promise) { ...
[ ] I/flutter (11005): 836704696 1 _JSFunction (key, val) => { this[key] = val; } ...
[ ] I/flutter (11005): 285986741 1 _JSFunction function bound tab2Tab1() { ...
[ ] I/flutter (11005): 911571841 1 _JSFunction function bound tab2Tab2() { ...
[ ] I/flutter (11005): 755609514 1 _JSFunction function bound tab2Tab3() { ...
这条异常打印出了一个排版比较整齐的表格,里面给出的是在引擎销毁时仍然没有被释放的对象。
这行 log 是由 flutter_js 库的 quickjs/ffi.dart 的 jsFreeRuntime 方法中实现。
是否会导致内存泄漏?答案是不会,因为 jsFreeRuntime 方法中,从底层对这些变量进行了释放:ref.destroy()。